/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/

/**
 * @brief Implement operations for type check for AMC.
 *
 * @author  WXQ#XDU
 * @date    2024.12
 */

#include <assert.h>
#include <stdbool.h>

#include "../include/typesys.h"


bool type_is_compatible ( t_type_ptr typeTarget,
                          t_type_ptr typeSource,
                          bool targetIsParameter)
{
    if ( typeTarget == typeSource )  // compare oneself
        return true;

    EnumTypeKind  kindTarget = type_kind(typeTarget);
    EnumTypeKind  kindSource = type_kind(typeSource);
    if ( kindTarget != kindSource )
        return false;

    // AMC dose not support function pointer
    if (kindTarget == AMCC_TYPE_ERR || kindTarget == AMCC_TYPE_FUNC)
        return false;

    EnumTokenKind baseKindTarget = type_var_basekind (typeTarget);
    EnumTokenKind baseKindSource = type_var_basekind (typeSource);
    bool baseIsCompatible =
            (baseKindSource == baseKindTarget)
            || (baseKindTarget == TK_TYPE_INT && baseKindSource == TK_TYPE_DOUBLE)
            || (baseKindSource == TK_TYPE_INT && baseKindTarget == TK_TYPE_DOUBLE)
            ;
    if (kindTarget == AMCC_TYPE_BASE) {  // two basic types
        return baseIsCompatible;
    }

    // For two array types, return TRUE ONLY IF
    //  (1) both element types are compatible, and
    //  (2) both dimensions are same, and
    //  (3) the length of the same dimension is same.

    if (false == baseIsCompatible)
        return false;

    const int * dimsTarget = type_var_diminfo(typeTarget);
    const int * dimsSource = type_var_diminfo(typeSource);

    // both dimensions are not same
    if (dimsTarget[0] != dimsSource[0] || dimsTarget[0] < 0)
        return false;

    // don't check the length of first dimension for parameter
    int dimCounter = targetIsParameter ? 2 : 1;
    for ( ; dimCounter <= dimsTarget[0]; ++dimCounter) {
        if (dimsTarget[dimCounter] != dimsSource[dimCounter])
            return false;
    }

    return true;
}


static bool basic_is_equiv_(t_type_ptr typeTarget, t_type_ptr typeSource);
static bool array_is_equiv_(t_type_ptr typeTarget, t_type_ptr typeSource);
static bool func_is_equiv_(t_type_ptr typeTarget, t_type_ptr typeSource);

bool type_is_equiv(t_type_ptr typeTarget, t_type_ptr typeSource) {
    assert (NULL != typeTarget );
    assert (NULL != typeSource );
    if ( typeTarget == typeSource ) // compare oneself
        return true;

    EnumTypeKind  kindTarget = type_kind(typeTarget);
    EnumTypeKind  kindSource = type_kind(typeSource);
    if ( kindTarget != kindSource )
        return false;

    if (kindTarget == AMCC_TYPE_BASE) {
        return basic_is_equiv_(typeTarget, typeSource);
    } else if (kindTarget == AMCC_TYPE_ARRAY) {
        return array_is_equiv_(typeTarget, typeSource);
    } else if (kindTarget == AMCC_TYPE_FUNC) {
        return func_is_equiv_(typeTarget, typeSource);
    }

    return false;
}


// For two basic types, return TRUE ONLY IF they are a same type
static bool basic_is_equiv_(t_type_ptr typeTarget, t_type_ptr typeSource) {
    EnumTokenKind baseKindTarget = type_var_basekind (typeTarget);
    EnumTokenKind baseKindSource = type_var_basekind (typeSource);
    return (baseKindSource == baseKindTarget) ;
}

// For two array types, return TRUE ONLY IF
//  both array dimensions are equivalent ,
//  and the lengths of the same dimension is same
//  and the elements types are same
//
static bool array_is_equiv_(t_type_ptr typeTarget, t_type_ptr typeSource) {
    const int * dimsTarget = type_var_diminfo(typeTarget);
    const int * dimsSource = type_var_diminfo(typeSource);
    if (dimsTarget[0] != dimsSource[0])
        return false;

    // compare the elements type
    EnumTokenKind baseKindTarget = type_var_basekind (typeTarget);
    EnumTokenKind baseKindSource = type_var_basekind (typeSource);
    if (baseKindSource != baseKindTarget)
        return false;

    for (int k=1; k<dimsTarget[0]; ++k) {
        if (dimsTarget[k] != dimsSource[k])
            return false;
    }

    return true;
}



// For two function types, return TRUE ONLY IF
//  both return value types are equivalent ,
//  and the numbers of parameters are same in order,
//  and the types of parameters are equivalent  in order,
//
static bool func_is_equiv_(t_type_ptr typeTarget, t_type_ptr typeSource) {
    t_type_ptr aTypeTarget = type_func_return(typeTarget);
    t_type_ptr aTypeSource = type_func_return(typeSource);
    if (false == type_is_equiv(aTypeTarget, aTypeSource))
        return false;

    t_list_ptr paramListTarget = type_func_parameters(typeTarget);
    t_list_ptr paramListSource = type_func_parameters(typeSource);
    int  nParamTarget = list_size(paramListTarget);
    int  nParamSource = list_size(paramListTarget);
    if (nParamTarget != nParamSource)
        return false;

    t_type_ptr * paramTypesTarget = (t_type_ptr *) list_to_array(paramListTarget);
    t_type_ptr * paramTypesSource = (t_type_ptr *) list_to_array(paramListSource);
    for (int k=0; k<nParamTarget; ++k) {
        if (false == type_is_equiv(paramTypesTarget[k], paramTypesSource[k]))
            return false;
    }

    return true;
}
